// ***************************************************************************
// CHANGE LOG
//
// ***************************************************************************

#include <p32xxxx.h>
#include <plib.h>
#include <string>
#include "Spi.h"

/*! \brief Initializes an SPI perpherial.
 *
 * Sets appropriate registers for SPI communications using the specified 
 * SPI channel.
 *
 * SPI device is configured for Master Mode and transmits devices on the
 * falling edge of the clock with a clock speed of 5MHz.
 * 
 * The chip select feature is disabled to utilize a shared bus.
 *
 * @param channel SPI Channel to initialize.
 * @param clockSpeed SPI clock speed.
 * @param sampleLocation SPI sample location.
 */
void Spi::Initialize(
				 SpiChannel channel,
				 UINT32 clockSpeed,
				 Spi::SampleLocation sampleLocation)
{
	SpiChnConfigure(
				channel,
				(SpiConfigFlags) (
							SPI_CONFIG_MSTEN |
							(SpiConfigFlags) sampleLocation));

	SpiChnSetBitRate(channel, PB_CLK, clockSpeed);
	SpiChnEnable(channel, true);
}

/*! \brief Closes the SPI Port and returns pins to their default value.
 */
void Spi::Close(SpiChannel channel)
{
	SpiChnClose(channel);
}

/*! \brief Closes the SPI Port and returns pins to their default value.
 */
void Spi::Close()
{
	SpiChnClose(this->_spiChannel);
	this->_isOpen = false;
}

/*! \brief Returns true if the SPI port is open.
 */
bool Spi::IsOpen()
{
	return this->_isOpen;
}

/*! \brief Initializes a new instance of the SPI class.
 *
 * Initializes a new instance of the SPI class.
 *
 * @param channel SPI channel to use for communications
 * @param csPort Chip Select Port
 * @param pin Chip Select pin
 */
Spi::Spi(SpiChannel channel, IoPortId csPort, UINT32 pin)
{
	this->SetConfiguration(channel, csPort, pin);
}

void Spi::SetConfiguration(
					  SpiChannel channel,
					  IoPortId csPort,
					  UINT32 pin)
{
	this->_spiChannel = channel;
	this->_csPort = csPort;
	this->_pin = pin;

	PORTSetPinsDigitalOut(csPort, pin);
	PORTSetBits(this->_csPort, this->_pin);

	this->_isOpen = true;
}

/*! \brief Changes the Chip Select pin to its default value.
 */
Spi::~Spi()
{
	this->Close();
	PORTSetPinsDigitalIn(this->_csPort, this->_pin);
}

/*! \brief Writes a single byte using the Serial Peripheral Interface device.
 *
 * @param data Data byte to send
 */
void Spi::Write(unsigned char data)
{
	if (!this->_isOpen)
		return;

	// Clear SPI Input Buffer
	while (SpiChnDataRdy(this->_spiChannel) == TRUE)
		SpiChnGetC(this->_spiChannel);

	// Write Data
	SpiChnPutC(this->_spiChannel, data);

	// Wait for Tx to complete
	while (SpiChnDataRdy(this->_spiChannel) == FALSE);
	SpiChnGetC(this->_spiChannel);
}

/*! \brief Writes multiple bytes using the Serial Peripheral Interface device.
 *
 * @param buffer Pointer to the buffer of the data to send.
 * @param length Number of bytes to send.
 */
void Spi::Write(const unsigned char *buffer, int length)
{
	if (!this->_isOpen)
		return;

	for (int i = 0; i < length; i++)
	{
		this->Write(buffer[i]);
	}
}

/*! \brief Writes multiple bytes using the Serial Peripheral Interface device.
 *
 * @param buffer Pointer to the buffer of the data to send.
 * @param index Position in array to start sending.
 * @param length Number of bytes to send.
 */
void Spi::Write(const unsigned char *buffer, int index, int length)
{
	if (!this->_isOpen)
		return;

	for (int i = index; i < index + length; i++)
	{
		this->Write(buffer[i]);
	}
}

/*! \brief Writes a std::string using the Serial Peripheral Interface device.
 *
 * @param data std::string to send
 */
void Spi::Write(std::string data)
{
	if (!this->_isOpen)
		return;

	for (int i = 0; i < data.length(); i++)
		this->Write(data[i]);
	// Null terminate std::string
	this->Write(0);
}

/*! \brief Reads a single byte using the Serial Peripheral Interface device.
 *
 * @returns Byte read from SPI
 */

unsigned char Spi::Read()
{
	unsigned char data;

	if (!this->_isOpen)
		return 0;

	while (SpiChnDataRdy(this->_spiChannel))
		SpiChnReadC(this->_spiChannel);

	SpiChnGetRov(this->_spiChannel, true);

	this->Write(0b01010101);
	data = SpiChnReadC(this->_spiChannel);

	return data;
}

/*! \brief Reads multiple bytes using the Serial Peripheral Interface device.
 *
 * @param buffer Pointer to the buffer to fill with received data.
 * @param length Number of bytes to receive.
 */
void Spi::Read(unsigned char *buffer, int length)
{
	if (!this->_isOpen)
		return;

	for (int i = 0; i < length; i++)
		buffer[i] = this->Read();
}

/*! \brief Reads a std::string using the Serial Peripheral Interface device.
 *
 * Reads data from the Serial Peripheral Interface device and and replaces the
 * contents of the buffer with the received data NOT including the 
 * std::string terminator.
 *
 * This function returns when a std::string terminator is read.
 *
 * @param buffer Pointer to the std::string buffer to fill with received data.
 */
void Spi::Read(std::string *buffer)
{
	unsigned char data;
	if (!this->_isOpen)
		return;

	buffer->clear();

	while (true)
	{
		data = this->Read();

		if (data == '\0')
			return;

		*buffer += data;
	}

	return;
}

/*! \brief Reads a std::string of the specified length using the Serial
 * Peripheral Interface device.
 *
 * Reads data from the Serial Peripheral Interface device and and replaces the
 * contents of the buffer with the received data.
 *
 * This function returns when either length characters are received or a NULL
 * std::string terminator is read.
 *
 * If a NULL std::string is encountered before length characters are read, 
 * it is NOT appended to the std::string.
 *
 * @param buffer Pointer to the std::string buffer to fill with received data.
 * @param length Maximum number of characters to read.
 */
void Spi::Read(std::string *buffer, int length)
{
	unsigned char data;
	if (!this->_isOpen)
		return;


	buffer->clear();

	for (int i = 0; i < length; i++)
	{
		data = this->Read();

		if (data == '\0')
			return;

		*buffer += data;
	}
}

/*! \brief Controls the Chip Select pin
 *
 * @param selected If TRUE, pin is driven LOW, otherwise pin is driven HIGH.
 */
void Spi::ChipSelect(bool selected)
{
	if (!this->_isOpen)
		return;

	if (selected)
	{
		PORTClearBits(this->_csPort, this->_pin);
	}
	else
	{
		PORTSetBits(this->_csPort, this->_pin);
	}
}

